package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"math"
	"os"
	"os/exec"
	"strings"

	"github.com/spf13/pflag"
)

type selpgArgs struct {
	startPage int
	endPage   int
	pageLen   int
	pageType  bool
	printDest string
}

func main() {
	args := new(selpgArgs)
	pflag.IntVarP((&args.startPage), "start_page", "s", -1, "Start page (Required)")
	pflag.IntVarP((&args.endPage), "end_page", "e", -1, "End page (Required)")
	pflag.IntVarP((&args.pageLen), "page_length", "l", 72, "Page length (Optional)")
	pflag.BoolVarP((&args.pageType), "page_type", "f", false, "Delimited by '\\f' (Optional)")
	pflag.StringVarP((&args.printDest), "print_dest", "d", "", "Printer name (Optional)")
	pflag.Parse()

	processArgs(args)
	processInput(args)
}

func processArgs(args *selpgArgs) {
	if args.startPage == -1 || args.endPage == -1 {
		os.Stderr.Write([]byte("no enough arguments\n"))
		os.Exit(0)
	}
	if args.startPage < 1 || args.startPage > (math.MaxInt32-1) {
		os.Stderr.Write([]byte("invalid start page\n"))
		os.Exit(1)
	}
	if args.endPage < 1 || args.endPage > (math.MaxInt32-1) || args.endPage < args.startPage {
		os.Stderr.Write([]byte("invalid end page\n"))
		os.Exit(2)
	}
	if args.pageLen == -1 && args.pageType == false {
		os.Stderr.Write([]byte("'-lNumber' and '-f' are conflict\n"))
		os.Exit(3)
	}
	if args.pageLen < 1 || args.pageLen > (math.MaxInt32-1) {
		os.Stderr.Write([]byte("invalid page length\n"))
		os.Exit(4)
	}
	if pflag.NArg() > 1 {
		os.Stderr.Write([]byte("too many arguments\n"))
		os.Exit(5)
	}
}

func processInput(args *selpgArgs) {
	var reader *bufio.Reader
	if pflag.NArg() == 0 {
		reader = bufio.NewReader(os.Stdin)
	} else {
		file, err := os.Open("./" + pflag.Arg(0))
		defer file.Close()
		if err != nil {
			os.Stderr.Write([]byte("open file error\n"))
			os.Exit(6)
		}
		reader = bufio.NewReader(file)
	}

	var writer strings.Builder
	pageCtr := 1
	lineCtr := 0

	if args.pageType {
		for {
			c, err := reader.ReadByte()
			if err == io.EOF {
				break
			} else if err != nil {
				os.Stderr.Write([]byte("read file error\n"))
				os.Exit(7)
			}

			if c == '\f' {
				pageCtr++
			}

			if pageCtr >= args.startPage && pageCtr <= args.endPage {
				err := writer.WriteByte(c)
				if err != nil {
					os.Stderr.Write([]byte("write byte error\n"))
					os.Exit(8)
				}
			}
		}
	} else {
		for {
			crc, err := reader.ReadBytes('\n')
			if err == io.EOF {
				break
			} else if err != nil {
				os.Stderr.Write([]byte("read file error\n"))
				os.Exit(7)
			}

			lineCtr++
			if lineCtr > args.pageLen {
				pageCtr++
				lineCtr = 1
			}

			if pageCtr >= args.startPage && pageCtr <= args.endPage {
				_, errW := writer.Write(crc)
				if errW != nil {
					os.Stderr.Write([]byte("write bytes error\n"))
					os.Exit(9)
				}
			}
		}
	}

	if pageCtr < args.startPage {
		os.Stderr.Write([]byte("start page is greater than total pages\n"))
		os.Exit(10)
	}
	if pageCtr < args.endPage {
		os.Stderr.Write([]byte("end page is greater than total pages\n"))
		os.Exit(11)
	}

	if args.printDest == "" {
		fmt.Printf("%s", writer.String())
	} else {
		cmd := exec.Command("lp", "-d"+args.printDest)
		cmd.Stdin = strings.NewReader(writer.String())
		var out bytes.Buffer
		cmd.Stdout = &out
		err := cmd.Run()
		if err != nil {
			os.Stderr.Write([]byte("write to printer fail\n"))
			os.Exit(12)
		}
	}
}
